﻿//
// *************************************************************************************
// WP-Framework
// 
// Developed by
// Lukas Kretschmar (lkretschmar.dev@bluewin.ch)
//
// Switzerland, Copyright (c) 2013 lkretschmar
// This content is released under the MIT License (http://opensource.org/licenses/MIT).
//     
// *************************************************************************************
//
//
			

using System;
using KretschIT.WP_Fx.Core.Validation;
using System.Collections.Generic;
using System.Reflection;
using KretschIT.WP_Fx.EasyMoq.Generator.Usings;
using System.Linq;

namespace KretschIT.WP_Fx.EasyMoq.Generator.Items
{
    internal class MockableInfo : IMockableInfo
    {
        private const string MockableSuffix = "Mockable";
        private const int GenericNameEndingLength = 2;

        private IUsingsBuilder usings = new UsingsBuilder();
        private INamingFactory nameFactory;

        public MockableInfo(Type interfaceType, INamingFactory nameFactory)
        {
            It.IsNotNull(() => interfaceType);
            It.IsNotNull(() => nameFactory);
            
            this.nameFactory = nameFactory;
            var abstractInterfaceType = this.GetAbstractType(interfaceType);
            this.InterfaceType = abstractInterfaceType;
            this.InitGenericVersion(interfaceType);
        }

        private Type GetAbstractType(Type type)
        {
            if (type.IsGenericType)
            {
                type = type.GetGenericTypeDefinition();
            }
            return type;
        }

        private void InitGenericVersion(Type type)
        {
            if (type.IsGenericType)
            {
                this.AddGenericTypes(type.GetGenericArguments());
            }
        }

        #region Interface

        public Type InterfaceType { get; private set; }

        public string InterfaceName
        {
            get 
            {
                return this.nameFactory.GetNormalizedNameOf(this.InterfaceType);
            }
        }

        #endregion

        #region Mockable

        public string MockableName
        {
            get
            {
                return this.nameFactory.CreateMockableNameFor(this.InterfaceType);
            }
        }

        #endregion

        #region Namespaces

        public string InterfaceNamespace
        {
            get { return this.InterfaceType.Namespace; }
        }

        public IEnumerable<string> UsingNamespaces
        {
            get
            {
                if (!this.usings.Usings.Any())
                {
                    this.BuildUsings();
                }
                return usings.Usings;
            }
        }

        #region Build Usings

        private void BuildUsings()
        {
            var usedTypes = GetUsedTypes();
            foreach (var type in usedTypes)
            {
                this.usings.Add(type);
            }
        }

        private IEnumerable<Type> GetUsedTypes()
        {
            if (this.IsGeneric)
            {
                return this.GetUsedTypesOfVersions();
            }
            return this.nameFactory.GetUsedTypesOf(this.InterfaceType);
        }

        private IEnumerable<Type> GetUsedTypesOfVersions()
        {
            var usedTypes = new List<Type>();
            foreach (var version in this.genericVersions)
            {
                var concreteType = this.InterfaceType.MakeGenericType(version.ToArray());
                usedTypes.AddRange(this.nameFactory.GetUsedTypesOf(concreteType));
            }
            return usedTypes;
        }
 
        #endregion

        #endregion

        public bool IsGeneric
        {
            get { return this.InterfaceType.IsGenericType; }
        }

        public bool Represents(Type type)
        {
            if (type != null)
            {
                type = this.GetAbstractType(type);
            }
            return this.InterfaceType.Equals(type);
        }

        public void AddGenericTypes(IEnumerable<Type> types)
        {
            if (this.IsGeneric && this.CanBeAddedAsGenericVersion(types))
            {
                genericVersions.Add(types);
            }
        }

        private bool CanBeAddedAsGenericVersion(IEnumerable<Type> types)
        {
            return this.NotNull(types) && this.HasOnlyValidTypes(types) && this.HasSameCountOfTypes(types) && !this.AlreadyExists(types);
        }

        private bool NotNull(IEnumerable<Type> types)
        {
            return types != null;
        }

        private bool HasOnlyValidTypes(IEnumerable<Type> types)
        {
            return types.All(t => t != null);
        }

        private bool HasSameCountOfTypes(IEnumerable<Type> types)
        {
            return types.Count() == this.InterfaceType.GetGenericArguments().Count();
        }
        
        private bool AlreadyExists(IEnumerable<Type> types)
        {
            foreach (var v in this.genericVersions)
            {
                return this.CompareTo(v, types);
            }
            return false;
        }

        private bool CompareTo(IEnumerable<Type> v, IEnumerable<Type> types)
        {
            for (int i = 0; i < types.Count(); i++)
            {
                if (v.ElementAt(i) != types.ElementAt(i))
                {
                    return false;
                }
            }
            return true;
        }

        private List<IEnumerable<Type>> genericVersions = new List<IEnumerable<Type>>();

        public IEnumerable<IEnumerable<Type>> GenericVersions
        {
            get
            {
                return this.genericVersions;
            }
        }
    }
}
